package net.sudot.flow.dsl.model;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import groovy.lang.MetaProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.junit.Assert;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author tangjialin on 2020-03-16.
 */
@Getter
@Setter
@Accessors(chain = true)
public class Context extends Assert implements GroovyObject {
    private static final TypeReference<Map<String, Object>> TYPE_REFERENCE = new TypeReference<Map<String, Object>>() {
    };
    private transient MetaClass metaClass;
    private transient Set<String> metaClassProperties;
    private transient Map<String, Object> properties = new HashMap<>();
    private HttpServletRequest request;
    private HttpServletResponse response;
    /** 输入参数 */
    private Map<String, Object> inputData;
    /** 每一次请求的响应参数 */
    private Map<String, Object> responseData = new HashMap<>();
    /** 经过 Flow.pre 处理的输入参数 */
    private Object data;
    private Map<String, Object> variables;

    public Context() {
        setMetaClass(getDefaultMetaClass());
    }

    public Context setData(Object data) {
        this.data = data;
        properties = new ObjectMapper().convertValue(data, TYPE_REFERENCE);
        return this;
    }

    @Override
    public Object getProperty(String property) {
        if (metaClassProperties.contains(property)) {
            return getMetaClass().getProperty(this, property);
        } else {
            return properties.get(property);
        }
    }

    @Override
    public void setProperty(String property, Object newValue) {
        if (metaClassProperties.contains(property)) {
            getMetaClass().setProperty(this, property, newValue);
        } else {
            properties.put(property, newValue);
        }
    }

    @Override
    public Object invokeMethod(String name, Object args) {
        return getMetaClass().invokeMethod(this, name, args);
    }

    @Override
    public MetaClass getMetaClass() {
        return this.metaClass;
    }

    @Override
    public void setMetaClass(MetaClass metaClass) {
        this.metaClass =
                null == metaClass
                        ? getDefaultMetaClass()
                        : metaClass;
        this.metaClassProperties = this.metaClass.getProperties()
                .stream().map(MetaProperty::getName).collect(Collectors.toSet());
    }

    private MetaClass getDefaultMetaClass() {
        return InvokerHelper.getMetaClass(this.getClass());
    }
}
